/*
 * Copyright (C) 2010-2018 Gordon Fraser, Andrea Arcuri and EvoSuite
 * contributors
 *
 * Copyright (C) 2021- SmartUt contributors
 *
 * SmartUt is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3.0 of the License, or
 * (at your option) any later version.
 *
 * SmartUt is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with SmartUt. If not, see <http://www.gnu.org/licenses/>.
 */
package org.smartut.continuous;

import org.smartut.Properties;
import org.smartut.Properties.AvailableSchedule;

/**
 * This class contain the starting, fixed configurations for CTG
 * @author arcuri
 *
 */
public class CtgConfiguration {

	/**
	 * To run a job, you need a minimum of RAM.
	 * If not enough RAM, then no point in even trying to start
	 * a search.
	 * Note: this include the memory of both the master and
	 * clients together 
	 */
	protected final int MINIMUM_MEMORY_PER_JOB_MB = 500;
	
	/**
	 * how much max memory should be used at the same time
	 * among all the parallel CTG runs? 
	 */
	public  final int totalMemoryInMB;	
	
	/**
	 * Number of cores CTG is allowed to use
	 */
	public final int numberOfCores;	
	
	/**
	 * for how long CTG is allowed to run
	 */
	public final int timeInMinutes;
	
	/**
	 * The minimum amount of minutes a search/job should run.
	 * Less than that, and there would be no point to even run
	 * the search.
	 */
	public final int minMinutesPerJob;
	
	/**
	 * Should we call home to upload status/usage statistics?
	 */
	public final boolean callHome;
	
	/**
	 * The type of job scheduler CTG will use
	 */
	public final AvailableSchedule schedule;

	/**
	 * Extra parameters for the test data generation jobs.
	 * Should only be used for experiments/debugging
	 */
	public final String extraArgs;


	public CtgConfiguration(int totalMemoryInMB, int numberOfCores,
			int timeInMinutes, int minMinutesPerJob, boolean callHome,
			AvailableSchedule schedule) {
		this(totalMemoryInMB,numberOfCores,timeInMinutes,minMinutesPerJob,callHome,schedule,"");
	}

	public CtgConfiguration(int totalMemoryInMB, int numberOfCores,
							int timeInMinutes, int minMinutesPerJob, boolean callHome,
							AvailableSchedule schedule, String extraArgs) {
		super();
		this.totalMemoryInMB = totalMemoryInMB;
		this.numberOfCores = numberOfCores;
		this.timeInMinutes = timeInMinutes;
		this.minMinutesPerJob = minMinutesPerJob;
		this.callHome = callHome;
		this.schedule = schedule;
		this.extraArgs = extraArgs;

		if(totalMemoryInMB < MINIMUM_MEMORY_PER_JOB_MB){
			throw new IllegalArgumentException("Should use at least "+MINIMUM_MEMORY_PER_JOB_MB+"MB");
		}
		if(numberOfCores < 1){
			throw new IllegalArgumentException("Need at least one core");
		}


		int requiredMemory = numberOfCores * MINIMUM_MEMORY_PER_JOB_MB;
		if(totalMemoryInMB < requiredMemory){
			throw new IllegalArgumentException(
					"Not enough memory assigned. You need at least "+MINIMUM_MEMORY_PER_JOB_MB+"MB per core." +
					" You are using "+numberOfCores+" cores for a total of "+totalMemoryInMB+"MB of memory." +
							" Decrease the number of cores or increase the total memory. See documentation."
			);
		}

	}

	/**
	 * Get instance based on values in {@link Properties}
	 * @return
	 */
	public static CtgConfiguration getFromParameters(){
			
		return new CtgConfiguration(
				Properties.CTG_MEMORY, 
				Properties.CTG_CORES, 
				Properties.CTG_TIME, 
				Properties.CTG_MIN_TIME_PER_JOB,
				false, /* TODO: just for now, as not implemented yet */
				Properties.CTG_SCHEDULE,
				Properties.CTG_EXTRA_ARGS
				);
	}

	/**
	 * Get new configuration with budget time proportional to the number of classes (and available cores)
	 * 
	 * 
	 * @param minutesPerClass
	 * @param numberOfCUTs
	 * @return
	 * @throws IllegalArgumentException
	 */
	public  CtgConfiguration getWithChangedTime(int minutesPerClass, int numberOfCUTs) throws IllegalArgumentException{
		
		if(minutesPerClass < 0){
			throw new IllegalArgumentException("Invalid value for minutesPerClass:" + minutesPerClass);
		}
		if(numberOfCUTs < 0){
			throw new IllegalArgumentException("Invalid value for numberOfCUTs:"+numberOfCUTs);
		}

		int time = (int) Math.ceil((minutesPerClass * numberOfCUTs) / (double) this.getNumberOfUsableCores());
		
		return new CtgConfiguration(
				this.totalMemoryInMB, 
				this.numberOfCores, 
				time, 
				this.minMinutesPerJob,
				this.callHome, 
				this.schedule,
				this.extraArgs
				);
	}


	public int getNumberOfUsableCores() {

		return numberOfCores;

		//shouldn't silently reduce number of cores if not enough memory
//		if(numberOfCores * MINIMUM_MEMORY_PER_JOB_MB <=  totalMemoryInMB) {
//			return numberOfCores;
//		} else {
//			return totalMemoryInMB / MINIMUM_MEMORY_PER_JOB_MB;
//		}
	}

	public int getConstantMemoryPerJob(){
		return  totalMemoryInMB / getNumberOfUsableCores() ;
	}
}
